﻿using System;
using System.Buffers;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Text;
using System.Threading;
using UMC.Data;
using UMC.Net;

namespace UMC.Host
{

    class HttpRequest : UMC.Net.Mime, IDisposable
    {
        public StringValue _scheme = StringValue.Http;
        public StringValue _host = StringValue.Empty;
        protected override bool Header(StringValue key, MimeHeadKey headKey, StringValue value)
        {
            switch (headKey)
            {
                case MimeHeadKey.XForwardedHost:
                    _host = value;
                    return false;
                case MimeHeadKey.Host:
                    if (_host.IsEmpty)
                        _host = value;
                    break;
                case MimeHeadKey.Connection:
                    _IsUpgrade = value.Contains("upgrade"u8, true);
                    break;
                case MimeHeadKey.XRealIP:
                case MimeHeadKey.XForwardedFor:
                    this._UserHostAddress = UTF7Encoding.UTF8.GetString(value.Span);
                    return false;
                case MimeHeadKey.XForwardedProto:
                    _scheme = value.Span.SequenceEqual(StringValue.Https.Span) ? StringValue.Https : StringValue.Http;
                    break;
                case MimeHeadKey.Upgrade:
                    _isWebSocket = Utility.EqualIgnoreCase(value.Span, "websocket"u8);
                    break;
                case MimeHeadKey.Cookie:
                    int begin = 0;
                    while (begin < value.Span.Length)
                    {
                        var vEnd = NetClient.IndexOf(value.Span, "; "u8, begin);

                        var vIndex = NetClient.IndexOf(value.Span, "="u8, begin);
                        if (vEnd > 0)
                        {
                            if (vEnd > vIndex && vIndex > 0)
                            {
                                AddCookie(value.Slice(begin, vIndex - begin), value.Slice(vIndex + 1, vEnd - vIndex - 1));
                            }
                            else
                            {
                                AddCookie(value.Slice(begin, vEnd - begin), StringValue.Empty);
                            }
                            begin = vEnd + 2;
                        }
                        else
                        {
                            if (vIndex > 0)
                            {
                                AddCookie(value.Slice(begin, vIndex - begin), value.Slice(vIndex + 1));

                            }
                            else
                            {
                                AddCookie(value.Slice(begin), StringValue.Empty);
                            }
                            break;
                        }
                    }
                    break;
            }
            return true;
        }
        void AddCookie(StringValue name, StringValue value)
        {
            this._Cookies.Add(new HeadValue(name, value));

        }

        HeadValues _Cookies = new HeadValues();
        public HeadValues Cookies
        {
            get
            {
                return _Cookies;
            }
        }
        internal HttpSocket _context;
        // internal long startTime;
        public HttpRequest(HttpSocket context, ByteChunk memory) : base(memory)
        {

            _context = context;
            this._remoteIpAddress = context.RemoteIpAddress;

        }
        public HttpMethod HttpMethod => Frist;
        public StringValue PathQuery
        {
            get
            {
                return Second;
            }
        }
        public StringValue Path
        {
            get
            {
                if (this.QueryIndex == -1)
                {
                    return Second;
                }
                else
                {
                    return Second.Slice(0, this.QueryIndex);
                }
            }
        }
        public StringValue Query
        {
            get
            {
                if (this.QueryIndex == -1)
                {
                    return StringValue.Empty;
                }
                else
                {
                    return Second.Slice(this.QueryIndex);
                }

            }
        }

        Uri _Referer;
        public Uri UrlReferrer
        {
            get
            {
                if (_Referer == null)
                {
                    var referer = this.Headers.Get("Referer"u8);

                    if (referer.IsEmpty == false)
                    {
                        try
                        {
                            _Referer = new Uri(referer.Span.UTF8());
                        }
                        catch
                        {

                        }
                    }
                }
                return _Referer;
            }
        }
        Uri _uri;
        public Uri Url
        {
            get
            {
                if (this._uri == null)
                {
                    this._uri = new Uri($"{_scheme}://{_host}{this.Second}");
                }
                return this._uri;
            }
        }
        public void RewriteUrl(StringValue pathAndQuery)
        {
            this._uri = null;
            this.Second = pathAndQuery;
            base.QueryIndex = this.Second.Span.IndexOf("?"u8);
        }
        String _remoteIpAddress, _UserHostAddress;
        public string UserHostAddress
        {
            get { return _UserHostAddress ?? _remoteIpAddress; }
        }
        public virtual string RemoteIP
        {
            get { return _remoteIpAddress; }
        }

        public new int QueryIndex => base.QueryIndex;
        bool _IsUpgrade, _isWebSocket;


        public override bool IsWebSocket => _isWebSocket && _IsUpgrade;
        protected override void Headed()
        {
            try
            {

                if (_host.IsEmpty)
                {
                    _host = new StringValue(_context.Host);
                }
                _context.PrepareRespone(this);
            }
            catch (Exception ex)
            {
                this.IsHttpFormatError = true;
                _context.Error(400, $"Bad Request \r\n{ex.Message}");
            }
        }

        byte[] _lastFormBuffer = Array.Empty<byte>();
        int lastFormBufferSize = 0;
        StringValue FormKey = StringValue.Empty;
        HeadValues _from = new HeadValues();

        public void ReadAsForm(Action<HeadValues> action)
        {
            if (this.ContentType.Span.IndexOf("form-urlencoded"u8, true) > -1)
            {
                this.ReadAsData((b, i, c) =>
                {
                    if (b.Length == 0)
                    {
                        if (i == 0 && c == 0)
                        {
                            this.FormValue(b, i, c);
                        }
                        if (_lastFormBuffer.Length > 0)
                        {
                            ArrayPool<byte>.Shared.Return(_lastFormBuffer);
                            lastFormBufferSize = 0;
                            _lastFormBuffer = Array.Empty<byte>();
                        }

                        action(_from);

                    }
                    else
                    {
                        this.FormValue(b, i, c);
                    }
                });
            }
            else
            {
                action(_from);
            }
        }

        public HeadValues Form
        {
            get
            {
                return _from;
            }
        }
        void FormValue(byte[] data, int offset, int size)
        {
            for (int i = 0; i < size; i++)
            {
                switch (data[offset + i])
                {
                    case 0x26:
                        StringValue value = StringValue.Empty;
                        if (lastFormBufferSize > 0)
                        {
                            value = new StringValue(WebUtility.UrlDecodeToBytes(_lastFormBuffer, 0, lastFormBufferSize));

                        }
                        if (FormKey.IsEmpty == false)
                        {
                            _from.Add(new HeadValue(FormKey, value));
                            FormKey = StringValue.Empty;

                        }
                        else if (value.IsEmpty == false)
                        {

                            _from.Add(new HeadValue(StringValue.Empty, value));
                        }
                        lastFormBufferSize = 0;
                        break;
                    case 0x3d:
                        FormKey = lastFormBufferSize == 0 ? StringValue.Empty : new StringValue(WebUtility.UrlDecodeToBytes(_lastFormBuffer, 0, lastFormBufferSize));


                        lastFormBufferSize = 0;
                        break;
                    default:
                        if (lastFormBufferSize == _lastFormBuffer.Length)
                        {
                            var b = ArrayPool<byte>.Shared.Rent(lastFormBufferSize + 0x100);
                            Array.Copy(_lastFormBuffer, 0, b, 0, lastFormBufferSize);
                            if (_lastFormBuffer.Length > 0)
                            {
                                ArrayPool<byte>.Shared.Return(_lastFormBuffer);
                            }
                            _lastFormBuffer = b;
                        }
                        _lastFormBuffer[lastFormBufferSize] = data[offset + i];
                        lastFormBufferSize++;
                        break;
                }
            }
            if (offset == 0 && size == 0 && data.Length == 0)
            {
                StringValue value = StringValue.Empty;
                if (lastFormBufferSize > 0)
                {
                    value = new StringValue(WebUtility.UrlDecodeToBytes(_lastFormBuffer, 0, lastFormBufferSize));

                }
                if (FormKey.IsEmpty == false)
                {
                    _from.Add(new HeadValue(FormKey, value));

                }
                else if (value.IsEmpty == false)
                {
                    _from.Add(new HeadValue(StringValue.Empty, value));
                }
                lastFormBufferSize = 0;
            }
        }
        Queue<Tuple<byte[], int>> _body = new Queue<Tuple<byte[], int>>();
        Net.NetWriteData _readData;
        protected override void Body(byte[] data, int offset, int size)
        {
            if (size > 0)
            {
                if (_readData != null)
                {
                    while (this._body.TryDequeue(out var d))
                    {
                        _readData(d.Item1, 0, d.Item2);
                        ArrayPool<byte>.Shared.Return(d.Item1);
                    }
                    _readData(data, offset, size);
                }
                else
                {
                    var d = ArrayPool<byte>.Shared.Rent(size);
                    Array.Copy(data, offset, d, 0, size);
                    _body.Enqueue(Tuple.Create(d, size));//bytes = d, size = size });
                }

            }
        }

        // Exception _Error;
        protected override void ReceiveError(Exception ex)
        {
            if (_readData != null)
            {
                while (this._body.TryDequeue(out var d))
                {
                    _readData(d.Item1, 0, d.Item2);
                    ArrayPool<byte>.Shared.Return(d.Item1);

                }
                _readData(Array.Empty<byte>(), -1, 0);
            }
            _context.OutputFinish();


        }
        public bool IsReadBody
        {
            get;
            private set;
        }
        public void ReadAsData(Net.NetWriteData readData)
        {
            if (IsReadBody == false)
            {
                IsReadBody = true;
                //lock (_sysc)
                {
                    if (this.IsHttpFormatError)
                    {
                        readData(Array.Empty<byte>(), -1, 0);
                    }
                    else if (this.isBodyFinish)
                    {
                        while (this._body.TryDequeue(out var d))
                        {
                            readData(d.Item1, 0, d.Item2);
                            ArrayPool<byte>.Shared.Return(d.Item1);

                        }

                        readData(Array.Empty<byte>(), 0, 0);
                    }
                    else
                    {
                        this._readData = readData;
                    }
                }

            }
            else
            {
                readData(Array.Empty<byte>(), this.IsHttpFormatError ? -1 : 0, 0);
            }
        }

        public override void Finish()
        {
            //lock (_sysc)
            {
                this.isBodyFinish = true;
                if (_readData != null)
                {

                    while (this._body.TryDequeue(out var d))
                    {
                        _readData(d.Item1, 0, d.Item2);
                        ArrayPool<byte>.Shared.Return(d.Item1);

                    }
                    _readData(Array.Empty<byte>(), 0, 0);

                }

            }

        }
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            while (this._body.TryDequeue(out var d))
            {
                ArrayPool<byte>.Shared.Return(d.Item1);

            }

            _Cookies.Dispose();
        }

        bool isBodyFinish = false;
    }

}
